package com.evildog.workspace.dependencies.web.advice;


import com.evildog.workspace.common.enums.CommonCustomExceptionDesc;
import com.evildog.workspace.common.exception.CommonException;
import com.evildog.workspace.common.intefaces.ICustomExceptionDesc;
import com.evildog.workspace.common.result.Result;
import com.evildog.workspace.common.result.ResultGenerator;
import com.evildog.workspace.common.util.FeignUtil;
import com.evildog.workspace.common.util.IpUtil;
import com.evildog.workspace.common.util.RequestUtil;
import com.evildog.workspace.common.util.WsAssert;
import com.evildog.workspace.dependencies.web.component.LogSaver;
import com.evildog.workspace.dependencies.web.enums.WebCustomExceptionDesc;
import com.evildog.workspace.dependencies.web.util.WsWebUtil;
import com.evildog.workspace.log.api.model.ExceptionLogParam;
import com.evildog.workspace.security.jwt.core.TokenUserInfo;
import com.evildog.workspace.security.jwt.util.SecurityUtil;
import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 统一异常处理类<br>
 * 捕获程序所有异常，针对不同异常，采取不同的处理方式
 *
 * @author simple
 * @author evildog
 * @since 2020-04-30
 */
@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class ExceptionHandleAdvice {

    @Value("${spring.application.name}")
    private String applicationName;

    @Autowired
    private LogSaver logSaver;

    /**
     * 其他异常
     */
    @ExceptionHandler({Exception.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<Object> handleException(HttpServletRequest request, Exception e) {
        log.error("Server Exception:", e);

        // 根据项目需求可取消
        ICustomExceptionDesc desc = new ICustomExceptionDesc() {
            @Override
            public String getCode() {
                return CommonCustomExceptionDesc.SERVER_ERROR.getCode();
            }

            @Override
            public String getMessage() {
                return ExceptionUtils.getMessage(e);
            }
        };

        // 记录异常
        try {
            ExceptionLogParam exceptionLog = new ExceptionLogParam();
            initExceptionLog(exceptionLog, request, e);
            // 执行插入 exceptionLog
            logSaver.addExceptionLog(exceptionLog);
        } catch (Exception ex) {
            log.error("记录异常日志失败!", ex);
        }


        return ResultGenerator.error(desc);
    }


    /**
     * 自定义异常
     */
    @ExceptionHandler({CommonException.class})
    public Result<Object> handleCommonException(CommonException e, HttpServletResponse response) {
        log.error("CommonException:{}", ExceptionUtils.getMessage(e));
        response.setStatus(e.getHttpStatus().value());
        return ResultGenerator.error(e);
    }

    /**
     * FeignException
     */
    @ExceptionHandler({FeignException.class})
    public Result<Object> handleFeignException(FeignException e, HttpServletResponse response) {
        log.error("FeignException: {}", ExceptionUtils.getMessage(e));
        response.setStatus(e.status());

        final String message = FeignUtil.getExceptionMessage(e);
        ICustomExceptionDesc desc = new ICustomExceptionDesc() {
            @Override
            public String getCode() {
                return CommonCustomExceptionDesc.FEIGN_EXCEPTION.getCode();
            }

            @Override
            public String getMessage() {
                return message;
            }
        };

        return ResultGenerator.error(desc);
    }

    /**
     * 校验异常
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("MethodArgumentNotValidException, {}", ExceptionUtils.getMessage(e));
        List<ObjectError> errors = e.getBindingResult().getAllErrors();
        StringBuilder errorMessage = new StringBuilder();
        for (ObjectError error : errors) {
            errorMessage.append(error.getDefaultMessage()).append("\n");
        }
        return ResultGenerator.error(CommonCustomExceptionDesc.BAD_REQUEST.getCode(), errorMessage.substring(0, errorMessage.length() - 1));
    }

    /**
     * Spring Assert异常
     */
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.PRECONDITION_FAILED)
    public Result<Object> handleIllegalArgumentException(IllegalArgumentException e) {
        log.error("IllegalArgumentException, {}", ExceptionUtils.getMessage(e));
        return ResultGenerator.error(CommonCustomExceptionDesc.BAD_REQUEST.getCode(), e.getMessage());
    }


    private void initExceptionLog(ExceptionLogParam exceptionLog, HttpServletRequest request, Exception e) {
        TokenUserInfo user = SecurityUtil.getUser();

        if (user != null) {
            exceptionLog.setUserId(user.getUserId());
        }

        exceptionLog.setExceptionName(e.getClass().getName())
                .setExceptionMessage(ExceptionUtils.getMessage(e))
                .setExceptionRootCauseMessage(ExceptionUtils.getRootCauseMessage(e))
                .setExceptionStackTrace(ExceptionUtils.getStackTrace(e))
                .setBody(WsWebUtil.getRequestBody(request))
        ;

        StackTraceElement[] stackTraceElements = e.getStackTrace();
        WsAssert.notEmpty(stackTraceElements, WebCustomExceptionDesc.STACKTRACE_EMPTY);

        StackTraceElement stackTraceElement = stackTraceElements[0];
        exceptionLog.setExceptionClassName(stackTraceElement.getClassName())
                .setExceptionFileName(stackTraceElement.getFileName())
                .setExceptionMethodName(stackTraceElement.getMethodName())
                .setExceptionLineNumber(stackTraceElement.getLineNumber())
                .setApplicationName(applicationName)
                .setUri(request.getRequestURI())
                .setMethod(request.getMethod())
                .setUserAgent(RequestUtil.getUserAgent())
                .setIp(IpUtil.getRealIp(request))
                .setExceptionTime(LocalDateTime.now())

        ;


    }
}
